連續幾天都在介紹理論跟我猜沒什麼人看的公式,是時候該帶大家動手做了,身為一個AI工程師今天就來從零開始實作AlphaGo吧。
開玩笑的,就不要自己造輪子了吧,何況我現在沒有chatGPT + Copilot已經不會寫code了,我怕造出來的輪子長得像下圖這樣。
今天我們要直接用大神造好的輪子!
分享一個知名的開源框架:AlphaZero General,是一個基於 AlphaGo Zero 的自我對弈強化學習框架,以下簡稱AZG。
作者是Surag Nair先生,剛剛看了一下他的linkedin,發現他都博士畢業一年了,雖然他絕對看不到這篇但還是恭喜他。
想當初2018年時,以為他是什麼很厲害的大叔,沒想到其實年紀跟我一樣大,而且也正在讀碩士,怎麼別人的side project都是這種拿到幾千顆星星的大型專案,而我只能寫些屎project(之後會分享),當年修類神經網路的課,我還是用AZG來完成我的期末project,感恩Surag Nair讚嘆Surag Nair。
我這樣也算是抱在巨人的大腿上吧。
只要他一抬腳,我也是能飛得又高又遠的。
最佛心的是他居然還有在更新,比起我2018年用的時候又更好用了,我們實驗室有幾位大神也是Contributors。
(安裝python跟各種環境這裡就不教學了,網路上多到數不清了。)
使用AZG的好處就是clone下來馬上就能動,只要執行main.py
馬上就開始訓練。
python main.py
連狀況都還沒搞清楚,他已經開始Self Play了,甚至都不知道是在Play什麼。
其實是他已經實作好了幾款遊戲的遊戲邏輯,包含Othello(黑白棋)、Tic Tac Toe、Connect4等遊戲,而且還有Pytorch版跟Keras版,真是貼心。
也可以換成自己想要的遊戲,只需要將遊戲邏輯實作出來即可,一樣可以套用AZG框架。
這邊也蠻清楚的,設定好參數就能開始訓練。
'numIters': 1000,
'numEps': 100, # Number of complete self-play games to simulate during a new iteration.
'tempThreshold': 15, #
'updateThreshold': 0.6, # During arena playoff, new neural net will be accepted if threshold or more of games are won.
'maxlenOfQueue': 200000, # Number of game examples to train the neural networks.
'numMCTSSims': 25, # Number of games moves for MCTS to simulate.
'arenaCompare': 40, # Number of games to play during arena play to determine if new net will be accepted.
'cpuct': 1,
numIters
訓練的總迭代次數,這裡預設是 1000。numEps
每次迭代中進行的自我對弈遊戲數量,就是那一執行馬上就開始的Self Play,這裡預設為 100。這邊在AlphaGoZero論文中是寫25000局,你可以根據不同遊戲的複雜度來設定,或是看你的硬體設備(財力)來決定。tempThreshold
溫度閥值,預設為 15。這跟探索策略有關,影響遊戲前期的隨機性,當遊戲超過一定回合後溫度會降低,從而減少隨機性。
updateThreshold
更新閾值,預設為 0.6。這是arenaCompare中新舊神經網路的對戰勝率,設為0.6代表新神經網路勝率高於60%時,就會更新。
maxlenOfQueue
訓練神經網路的遊戲樣本Queue的最大長度,預設為 200000,就是最多保留 200000 個遊戲樣本用於訓練。
numMCTSSims
每次行動中 MCTS 進行的模擬次數,預設為 25。這決定了在每次選擇動作時,MCTS 要進行多少次模擬,這邊在AlphaGoZero論文中是寫1600次,你可以根據不同遊戲的複雜度來設定,或是看你的硬體設備(財力)來決定。
arenaCompare
新舊網路進行對戰的場次,這裡預設為 40 場,我記得之前學長說400場會是一個比較好的數字,具體證明我已經忘了。
cpuct
這個就是PUCT公式中的那個常數C
這個C寫成
這邊預設為1,但其實 隨著模擬總數動態調整會比較好,具體要怎麼設置大家可以自行嘗試看看,或是參考其他論文的設定,這裡就不展開了。
這邊作者有提供黑白棋的pretrained model,所以直接執行pit.py
就可以跟電腦對戰了。
python pit.py
預設是8x8的黑白棋,有點感動他終於對齊了,2018年時預設的是6x6黑白棋,並且因為棋盤印出來是歪的,有點強迫症的我當初還花了點時間慢慢喬正,不知道作者是什麼時候更新的,感動。
本來想放我把AI電爆的畫面截圖,但我連玩了好幾場都輸,只能交給大家去嘗試了,還差點玩到忘記發文。
這段就是PUCT的實際程式碼,大家就可以自由發揮,比如把它改成LCB。
cur_best = -float('inf')
best_act = -1
for a in range(self.game.getActionSize()):
if valids[a]:
if (s, a) in self.Qsa:
u = self.Qsa[(s, a)] + self.args.cpuct * self.Ps[s][a] * math.sqrt(self.Ns[s]) / (1 + self.Nsa[(s, a)])
else:
u = self.args.cpuct * self.Ps[s][a] * math.sqrt(self.Ns[s] + EPS) # Q = 0 ?
if u > cur_best:
cur_best = u
best_act = a
這邊是處理葉節點與更新的程式碼,這邊也可以嘗試用之前介紹到的Quick Win、Big Win之類的方式去改進。
if s not in self.Ps:
self.Ps[s], v = self.nnet.predict(canonicalBoard)
valids = self.game.getValidMoves(canonicalBoard, 1)
self.Ps[s] = self.Ps[s] * valids # masking invalid moves
sum_Ps_s = np.sum(self.Ps[s])
if sum_Ps_s > 0:
self.Ps[s] /= sum_Ps_s # renormalize
else:
log.error("All valid moves were masked, doing a workaround.")
self.Ps[s] = self.Ps[s] + valids
self.Ps[s] /= np.sum(self.Ps[s])
self.Vs[s] = valids
self.Ns[s] = 0
return -v
if (s, a) in self.Qsa:
self.Qsa[(s, a)] = (self.Nsa[(s, a)] * self.Qsa[(s, a)] + v) / (self.Nsa[(s, a)] + 1)
self.Nsa[(s, a)] += 1
else:
self.Qsa[(s, a)] = v
self.Nsa[(s, a)] = 1
self.Ns[s] += 1
return -v
Maverick是目前世界上最強的黑白棋程式,作者為陳彥吉,連續拿了好幾屆的TCGA、TAAI、ICGA金牌,大家可以猜猜他是怎麼做的。
之前日本有開發世界最弱黑白棋程式,但其實黑白棋你只要能做出最強,那同時也可以最弱(只要把獲勝條件那邊加個負號就好了),那個標題其實只是一個噱頭吧,所以Maverick也是可以輕鬆比他還弱的,歡迎大家實作自己的黑白棋程式去參加比賽,挑戰這支多年連霸的冠軍程式。
這邊論文多到爆,歡迎大家選喜歡的遊戲去研究。